package github.saukiya.sxattribute.util.nms;

import github.saukiya.sxattribute.util.Reflection;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftMetaArmorStand;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class NMS_v1_12_R1 extends NMSBase {

    /**
     * 加载NBT反射类
     * 此类所附加的nbt均带有插件名
     *
     * @throws NoSuchMethodException  NoSuchMethodException
     * @throws ClassNotFoundException ClassNotFoundException
     */
    public NMS_v1_12_R1() throws Exception {

        String bukkitVersion = NMSUtils.getVersion();
        String packet = Bukkit.getServer().getClass().getPackage().getName();
        String nmsName = "net.minecraft.server." + bukkitVersion;
        xCraftItemStack = Class.forName(packet + ".inventory.CraftItemStack");

        Class<?> xNMSItemStack = Class.forName(nmsName + ".ItemStack");
        xNBTTagCompound = Class.forName(nmsName + ".NBTTagCompound");
        Class<?> xNBTTagString = Class.forName(nmsName + ".NBTTagString");
        Class<?> xNBTTagDouble = Class.forName(nmsName + ".NBTTagDouble");
        Class<?> xNBTTagInt = Class.forName(nmsName + ".NBTTagInt");
        xNBTTagList = Class.forName(nmsName + ".NBTTagList");
        Class<?> xNMSBase = Class.forName(nmsName + ".NBTBase");

        xNewNBTTagString = xNBTTagString.getConstructor(String.class);
        xNewNBTTagDouble = xNBTTagDouble.getConstructor(double.class);
        xNewNBTTagInt = xNBTTagInt.getConstructor(int.class);

        xAsNMSCopay = xCraftItemStack.getDeclaredMethod("asNMSCopy", ItemStack.class);
        xGetTag = xNMSItemStack.getDeclaredMethod("getTag");
        xHasTag = xNMSItemStack.getDeclaredMethod("hasTag");
        xSet = xNBTTagCompound.getDeclaredMethod("set", String.class, xNMSBase);
        xAdd = xNBTTagList.getDeclaredMethod("add", xNMSBase);
        xRemove = xNBTTagCompound.getDeclaredMethod("remove", String.class);
        xSetTag = xNMSItemStack.getDeclaredMethod("setTag", xNBTTagCompound);
        xAsBukkitCopy = xCraftItemStack.getDeclaredMethod("asBukkitCopy", xNMSItemStack);

        xHasKey = xNBTTagCompound.getDeclaredMethod("hasKey", String.class);
        xGet = xNBTTagCompound.getDeclaredMethod("get", String.class);
        xGetString = xNBTTagCompound.getDeclaredMethod("getString", String.class);
        xGetListString = xNBTTagList.getDeclaredMethod("getString", int.class);
        xSize = xNBTTagList.getDeclaredMethod("size");
    }

    @Override
    public void sendActionBar(Player player, String msg) {

        try {
            final Object chatComponentText = this.getNMSClass("ChatComponentText").getConstructor(String.class).newInstance(msg);
            final Object chatMessageType = this.getNMSClass("ChatMessageType").getField("GAME_INFO").get(null);
            final Object packetPlayOutChat = this.getNMSClass("PacketPlayOutChat").getConstructor(this.getNMSClass("IChatBaseComponent"), this.getNMSClass("ChatMessageType")).newInstance(chatComponentText, chatMessageType);
            final Object getHandle = player.getClass().getMethod("getHandle", (Class<?>[])new Class[0]).invoke(player);
            final Object playerConnection = getHandle.getClass().getField("playerConnection").get(getHandle);
            playerConnection.getClass().getMethod("sendPacket", this.getNMSClass("Packet")).invoke(playerConnection, packetPlayOutChat);
        }catch (Exception e){

            e.printStackTrace();
        }

    }

    public void initV1_14_R1(String nmsName) throws Exception {
        Class<?> xNMSItemStack = Class.forName(nmsName + ".ItemStack");
        xNBTTagCompound = Class.forName(nmsName + ".NBTTagCompound");
        Class<?> xNBTTagString = Class.forName(nmsName + ".NBTTagString");
        Class<?> xNBTTagDouble = Class.forName(nmsName + ".NBTTagDouble");
        Class<?> xNBTTagInt = Class.forName(nmsName + ".NBTTagInt");
        xNBTTagList = Class.forName(nmsName + ".NBTTagList");
        Class<?> xNMSBase = Class.forName(nmsName + ".NBTBase");

        xNewNBTTagString = xNBTTagString.getConstructor(String.class);
        xNewNBTTagDouble = xNBTTagDouble.getConstructor(double.class);
        xNewNBTTagInt = xNBTTagInt.getConstructor(int.class);

        xAsNMSCopay = xCraftItemStack.getDeclaredMethod("asNMSCopy", ItemStack.class);
        xGetTag = xNMSItemStack.getDeclaredMethod("getTag");
        xHasTag = xNMSItemStack.getDeclaredMethod("hasTag");
        xSet = xNBTTagCompound.getDeclaredMethod("set", String.class, xNMSBase);
        xAdd = xNBTTagList.getDeclaredMethod("add", int.class, xNMSBase);
        xRemove = xNBTTagCompound.getDeclaredMethod("remove", String.class);
        xSetTag = xNMSItemStack.getDeclaredMethod("setTag", xNBTTagCompound);
        xAsBukkitCopy = xCraftItemStack.getDeclaredMethod("asBukkitCopy", xNMSItemStack);

        xHasKey = xNBTTagCompound.getDeclaredMethod("hasKey", String.class);
        xGet = xNBTTagCompound.getDeclaredMethod("get", String.class);
        xGetString = xNBTTagCompound.getDeclaredMethod("getString", String.class);
        xGetListString = xNBTTagList.getDeclaredMethod("getString", int.class);
        xSize = xNBTTagList.getDeclaredMethod("size");
    }


    /**
     * 清除物品默认属性标签
     *
     * @param item ItemStack
     */
    public ItemStack clearAttribute(ItemStack item) {
        if (item != null) {
            try {
                Object nmsItem = xAsNMSCopay.invoke(xCraftItemStack, item);
                if (nmsItem != null) {
                    Object compound = ((boolean) xHasTag.invoke(nmsItem)) ? xGetTag.invoke(nmsItem) : xNBTTagCompound.newInstance();
                    Object modifiers = xNBTTagList.newInstance();
                    xSet.invoke(compound, "AttributeModifiers", modifiers);
                    xSetTag.invoke(nmsItem, compound);
                    item.setItemMeta(((ItemStack) xAsBukkitCopy.invoke(xCraftItemStack, nmsItem)).getItemMeta());
                }
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException e) {
                e.printStackTrace();
            }
        }
        return item;
    }

    /**
     * 获取全部NBT数据
     *
     * @param item ItemStack
     * @return String
     */
    public String getAllNBT(ItemStack item) {
        try {
            Object nmsItem = xAsNMSCopay.invoke(xCraftItemStack, item);
            if (nmsItem != null) {
                Object itemTag = ((boolean) xHasTag.invoke(nmsItem)) ? xGetTag.invoke(nmsItem) : xNBTTagCompound.newInstance();
                return "§c[" + item.getType().name() + ":" + item.getDurability() + "-" + item.hashCode() + "]§7 " + itemTag.toString().replace("§", "&");
            }
            return "§c[" + item.getType().name() + ":" + item.getDurability() + "-" + item.hashCode() + "]§7 §cNULL";
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException | NullPointerException e) {
            return null;
        }
    }

    /**
     * 设置物品NBT数据
     *
     * @param item  ItemStack
     * @param key   String
     * @param value String
     * @return ItemStack
     */
    public ItemStack setNBT(ItemStack item, String key, Object value) {
        try {
            Object nmsItem = xAsNMSCopay.invoke(xCraftItemStack, item);
            if (nmsItem != null) {
                Object itemTag = ((boolean) xHasTag.invoke(nmsItem)) ? xGetTag.invoke(nmsItem) : xNBTTagCompound.newInstance();
                Object tagString = xNewNBTTagString.newInstance(value.toString());
                xSet.invoke(itemTag, key, tagString);
                xSetTag.invoke(nmsItem, itemTag);
                item.setItemMeta(((ItemStack) xAsBukkitCopy.invoke(xCraftItemStack, nmsItem)).getItemMeta());
            }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }
        return item;
    }

    /**
     * 设置物品NBT数据 List 会被设置ItemMeta
     *
     * @param item ItemStack
     * @param key  String
     * @param list List
     * @return ItemStack
     */
    public ItemStack setNBTList(ItemStack item, String key, List<String> list) {
        try {
            Object nmsItem = xAsNMSCopay.invoke(xCraftItemStack, item);
            if (nmsItem != null) {
                Object itemTag = ((boolean) xHasTag.invoke(nmsItem)) ? xGetTag.invoke(nmsItem) : xNBTTagCompound.newInstance();
                Object tagList = xNBTTagList.newInstance();
                for (String str : list) {
                    xAdd.invoke(tagList, xNewNBTTagString.newInstance(str));
                }
                xSet.invoke(itemTag, key, tagList);
                xSetTag.invoke(nmsItem, itemTag);
                item.setItemMeta(((ItemStack) xAsBukkitCopy.invoke(xCraftItemStack, nmsItem)).getItemMeta());
            }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }
        return item;
    }

    /**
     * 获取物品NBT数据
     *
     * @param item ItemStack
     * @param key  String
     * @return String
     */
    public String getNBT(ItemStack item, String key) {
        try {
            Object nmsItem = xAsNMSCopay.invoke(xCraftItemStack, item);
            if (nmsItem != null) {
                Object itemTag = ((boolean) xHasTag.invoke(nmsItem)) ? xGetTag.invoke(nmsItem) : xNBTTagCompound.newInstance();
                if ((boolean) xHasKey.invoke(itemTag, key)) {
                    return (String) xGetString.invoke(itemTag, key);
                }
            }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 设置物品NBT数据 List
     *
     * @param item ItemStack
     * @param key  String
     * @return List
     */
    public List<String> getNBTList(ItemStack item, String key) {
        List<String> list = new ArrayList<>();
        try {
            Object nmsItem = xAsNMSCopay.invoke(xCraftItemStack, item);
            if (nmsItem != null) {
                Object itemTag = ((boolean) xHasTag.invoke(nmsItem)) ? xGetTag.invoke(nmsItem) : xNBTTagCompound.newInstance();
                Object tagList = (boolean) xHasKey.invoke(itemTag, key) ? xGet.invoke(itemTag, key) : xNBTTagList.newInstance();
                for (int i = 0; i < (Integer) xSize.invoke(tagList); i++) {
                    list.add((String) xGetListString.invoke(tagList, i));
                }
            }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 判断是否有物品NBT数据
     *
     * @param item ItemStack
     * @param key  String
     * @return Boolean
     */
    public boolean hasNBT(ItemStack item, String key) {
        try {
            Object nmsItem = xAsNMSCopay.invoke(xCraftItemStack, item);
            if (nmsItem != null) {
                Object itemTag = ((boolean) xHasTag.invoke(nmsItem)) ? xGetTag.invoke(nmsItem) : xNBTTagCompound.newInstance();
                if ((boolean) xHasKey.invoke(itemTag, key)) return true;
            }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 清除指定nbt
     *
     * @param item ItemStack
     * @param key  String
     * @return boolean
     */
    public boolean removeNBT(ItemStack item, String key) {
        try {
            Object nmsItem = xAsNMSCopay.invoke(xCraftItemStack, item);
            if (nmsItem != null) {
                Object itemTag = ((boolean) xHasTag.invoke(nmsItem)) ? xGetTag.invoke(nmsItem) : xNBTTagCompound.newInstance();
                if ((boolean) xHasKey.invoke(itemTag, key)) {
                    xRemove.invoke(itemTag, key);
                    xSetTag.invoke(nmsItem, itemTag);
                    item.setItemMeta(((ItemStack) xAsBukkitCopy.invoke(xCraftItemStack, nmsItem)).getItemMeta());
                }
                return true;
            }
            return false;
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException e) {
            return false;
        }
    }

    @Override
    public int getDurability(Object itemStack) {
        return ((ItemStack)itemStack).getDurability();
    }
    @Override
    public void invoke(Method method, Object o, Object... objects) {
        try {
            method.invoke(o, objects);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void sendTitle(Player player, String title, String subTitle, int a, int b, int c) {
        try {
            Method declaredMethod = Player.class.getDeclaredMethod("sendTitle", String.class, String.class, int.class, int.class, int.class);
            declaredMethod.setAccessible(true);
            declaredMethod.invoke(player, title, subTitle, a, b, c);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public ItemStack setDurability(Object itemStack, int value) {
        try {
            Reflection.invokeMethod(itemStack, "setDurability", (short)value);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            e.printStackTrace();
        }

        return (ItemStack) itemStack;
    }

    @Override
    public boolean isUnbreakable(Object object) {
        try {
            Method isUnbreakable = ItemMeta.class.getDeclaredMethod("isUnbreakable");
            return (boolean) isUnbreakable.invoke(object);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public ItemStack updateAttack(ItemStack item,double speed){
        if (item != null && !item.getType().name().equals("AIR")) {
            try {
                speed-=4;
                Object nmsItem = getXAsNMSCopay().invoke(getXCraftItemStack(), item);
                Object compound = ((Boolean) getXHasTag().invoke(nmsItem)) ? getXGetTag().invoke(nmsItem) : getXNBTTagCompound().newInstance();
                Object modifiers = getXNBTTagList().newInstance();
                Object attackSpeed = getXNBTTagCompound().newInstance();
                getXSet().invoke(attackSpeed, "AttributeName", getXNewNBTTagString().newInstance("generic.attackSpeed"));
                getXSet().invoke(attackSpeed, "Name", getXNewNBTTagString().newInstance("AttackSpeed"));
                getXSet().invoke(attackSpeed, "Amount", getXNewNBTTagDouble().newInstance(speed));
                getXSet().invoke(attackSpeed, "Operation", getXNewNBTTagInt().newInstance(0));
                getXSet().invoke(attackSpeed, "UUIDLeast", getXNewNBTTagInt().newInstance(20000));
                getXSet().invoke(attackSpeed, "UUIDMost", getXNewNBTTagInt().newInstance(1000));
                getXSet().invoke(attackSpeed, "Slot", getXNewNBTTagString().newInstance("mainhand"));
                invoke(getXAdd(),modifiers,attackSpeed);
                getXSet().invoke(compound, "AttributeModifiers", modifiers);
                getXSetTag().invoke(nmsItem, compound);
                item = (ItemStack) getXAsBukkitCopy().invoke(getXCraftItemStack(),nmsItem);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException e) {
                e.printStackTrace();
            }
        }
        return item;
    }

    @Override
    public ItemStack setUnbreakable(ItemStack itemStack, boolean unbreakable) {
        ItemMeta itemMeta = itemStack.getItemMeta();
        try {
            Method declaredMethod = ItemMeta.class.getDeclaredMethod("setUnbreakable", boolean.class);
            declaredMethod.setAccessible(true);
            declaredMethod.invoke(itemMeta,unbreakable);
            itemStack.setItemMeta(itemMeta);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return itemStack;
    }

    @Override
    public ItemStack getMainItem(Player player) {
        try {
            Method getEquipment = LivingEntity.class.getDeclaredMethod("getEquipment");
            Object invoke = getEquipment.invoke(player);
            Method getItemInHand = invoke.getClass().getDeclaredMethod("getItemInMainHand");
            return (ItemStack) getItemInHand.invoke(invoke);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
